/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ClassDeclaration, Decorator, MethodDeclaration, SourceFile } from 'ts-morph';
import { AbstractAnnotationAnalyzer } from './interface/AbstractAnnotationAnalyzer';
import { IConstantResolver } from './interface/IConstantResolver';
import { DecoratorParser } from './utils/DecoratorParser';
import { BaseAnalyzeResult } from '../../framework';
import { HMServiceResult } from '../model';
import { AnnotationConstants } from '../constants';
import { HMRouterExtensionContext } from '../HMRouterExtensionContext';

/**
 * Component Annotation Analyzer
 *
 * This analyzer is responsible for detecting and processing component-related
 * annotations in source files, including @HMService, @HMInterceptor, @HMLifecycle,
 * and other component decorators. It extracts metadata from both class-level and
 * method-level annotations.
 */
export class ComponentAnalyzer extends AbstractAnnotationAnalyzer {
  /**
   * Unique identifier for this analyzer
   */
  readonly name = 'ComponentAnalyzer';

  /**
   * Creates a new ComponentAnalyzer instance
   *
   * @param {IConstantResolver} constantResolver - Resolver for resolving constant references in annotations
   */
  constructor(constantResolver: IConstantResolver) {
    super(constantResolver);
  }

  /**
   * Analyzes a source file for component annotations
   *
   * This method examines classes and their methods in the source file,
   * looking for HMRouter-related annotations, and collects the results.
   *
   * @param {SourceFile} sourceFile - The source file to analyze
   * @param {string} filePath - Path to the source file
   * @param {HMRouterExtensionContext} context - Extension context for storing results
   */
  analyze(sourceFile: SourceFile, filePath: string, context: HMRouterExtensionContext): void {
    const results: BaseAnalyzeResult[] = [];

    // Process all class declarations in the source file
    sourceFile.getClasses().forEach((cls: ClassDeclaration) => {
      // Analyze class-level decorators first, then method-level decorators
      this.analyzeClassDecorators(cls, filePath, sourceFile, results);
      this.analyzeMethodDecorators(cls, filePath, sourceFile, results);
    });

    context.addAnalyzeResults(results);
  }

  /**
   * Analyze class-level decorators for HMRouter-related annotations
   *
   * This method checks for annotations like @HMService, @HMInterceptor,
   * @HMLifecycle, @HMAnimator, and @HMServiceProvider on classes.
   *
   * @param {ClassDeclaration} cls - The class declaration to analyze
   * @param {string} filePath - Path to the source file
   * @param {SourceFile} sourceFile - The source file object
   * @param {BaseAnalyzeResult[]} results - Array to store analysis results
   * @private
   */
  private analyzeClassDecorators(
    cls: ClassDeclaration,
    filePath: string,
    sourceFile: SourceFile,
    results: BaseAnalyzeResult[]
  ): void {
    cls.getDecorators().forEach((decorator: Decorator) => {
      // Check for HMRouter-related class decorators
      if (
      [
        AnnotationConstants.INTERCEPTOR_ANNOTATION, // @HMInterceptor
        AnnotationConstants.LIFECYCLE_ANNOTATION, // @HMLifecycle
        AnnotationConstants.ANIMATOR_ANNOTATION, // @HMAnimator
        AnnotationConstants.SERVICE_PROVIDE_ANNOTATION, // @HMServiceProvider
      ].includes(decorator.getName())
      ) {
        const result = this.addToResultSet(decorator, cls.getName()!, filePath, sourceFile);
        results.push(result);
      }
    });
  }

  /**
   * Analyze method-level decorators for HMRouter-related annotations
   *
   * Currently focuses on @HMService annotations on class methods,
   * which define service functions for the router.
   *
   * @param {ClassDeclaration} cls - The class declaration to analyze
   * @param {string} filePath - Path to the source file
   * @param {SourceFile} sourceFile - The source file object
   * @param {BaseAnalyzeResult[]} results - Array to store analysis results
   * @private
   */
  private analyzeMethodDecorators(
    cls: ClassDeclaration,
    filePath: string,
    sourceFile: SourceFile,
    results: BaseAnalyzeResult[]
  ): void {
    cls.getMethods().forEach((method: MethodDeclaration) => {
      method.getDecorators().forEach((decorator: Decorator) => {
        // Currently only handling @HMService on methods
        if (decorator.getName() === AnnotationConstants.SERVICE_ANNOTATION) {
          // For services, we need to store the method name for service mapping
          const serviceResult = this.addToResultSet(decorator, cls.getName()!, filePath, sourceFile) as HMServiceResult;
          serviceResult.functionName = method.getName();
          results.push(serviceResult);
        }
      });
    });
  }

  /**
   * Create an analysis result from a decorator
   *
   * This helper method processes a decorator and creates a result object
   * containing all the extracted metadata.
   *
   * @param {Decorator} decorator - The decorator to process
   * @param {string} componentName - The name of the component (class)
   * @param {string} filePath - Path to the source file
   * @param {SourceFile} sourceFile - The source file object
   * @returns {BaseAnalyzeResult} The analysis result object
   * @private
   */
  private addToResultSet(
    decorator: Decorator,
    componentName: string,
    filePath: string,
    sourceFile: SourceFile
  ): BaseAnalyzeResult {
    const decoratorResult = DecoratorParser.parseDecorator(decorator, this.constantResolver, sourceFile, filePath);

    decoratorResult.name = componentName;
    decoratorResult.sourceFilePath = filePath;

    return decoratorResult;
  }
}
